//-------------------------------------------------------------------
//	Copyright (c) 2013-2014  Zhirnov Andrey
//	This file is part of the "UX OpwnGL Framework" project.
//	See copyright notice in "UX_GL_Framework.h".
//-------------------------------------------------------------------

#include "Sample.h"


/*
=================================================
	constructor
=================================================
*/
	CSampleApplication::CSampleApplication () :
		cursorIsHidden(false), vsyncEnabled(false), wireframe(false)
	{
		eventListener = new SimulationEventListener( this );
	}
	
/*
=================================================
	destructor
=================================================
*/
	CSampleApplication::~CSampleApplication ()
	{
	}
	
/*
=================================================
	OnInit
=================================================
*/
	void CSampleApplication::OnInit ()
	{
		// generate height map
		const uvec2	dim( (VoxelTerrainBlockSize() * VoxelTerrainNumBlocks()).xy().To<uvec2>() + HEIGHT_MAP_BORDER*2 );

		FOR( i, heightMaps )
		{
			heightMaps[i] = new GLTexture2D();
			heightMaps[i]->Create( gl_tex_iformat::RG32F, uvec3(dim) );
			heightMaps[i]->SetSampler( GLSamplerSrc::Default_Bilinear_Clamp() );
		}
		

		// simple terrain shader
		ShaderLoader::Load( terrainData.prog, "shaders/tess_quad_terrain.prg" );
		terrainData.patchSize = terrainData.prog->GetTessVerticesCount();
		terrainData.unHeightMap.GetLocation( *terrainData.prog, "unHeightMap" );
		terrainData.matMVP.GetLocation( *terrainData.prog, "unMVPMatrix" );
		terrainData.ub.GetLocation( *terrainData.prog, "TerrainUB", 0 );
		

		// genrate grid
		const uint	grid_size	= 127;
		const uint	patch_size	= terrainData.patchSize;

		grid = new GLMesh();
		MeshGenerator::Grid( *grid, grid_size, 1.0f/grid_size, patch_size );
		

		// setup render states
		GLRenderState::TColorBuffer	cb;
		cb.bBlending	= false;

		terrainData.rs.aColorBuffers[0]		= cb;
		terrainData.rs.sDepth.bTest			= true;
		terrainData.rs.sDepth.bWrite		= true;
		terrainData.rs.sStates.bDepthClamp	= true;


		// setup camera
		const fvec2 wnd_size = Window()->Descriptor().vSize.To<fvec2>();
		camera.Create( transform_f(), DTR(60.0f), wnd_size.x/wnd_size.y, fvec2(1.0f, 1000.0f) );


		// init terrain simulation
		simulation.Create( VoxelTerrainPath(), VoxelTerrainBlockSize(), VoxelTerrainNumBlocks() );
		

		// other
		vsyncEnabled = true;
		Window()->SetVSync( vsyncEnabled );
	}
	
/*
=================================================
	OnFree
=================================================
*/
	void CSampleApplication::OnFree ()
	{
		simulation.Destroy();
		
		terrainData.ub.Destroy();
		terrainData.prog = null;
		
		heightMaps.Clear();

		grid		= null;
		heightMap	= null;
	}
	
/*
=================================================
	Draw
=================================================
*/
	void CSampleApplication::Draw ()
	{
		GLDefaultFrameBuffer::ClearColor( vec4(0.3f, 0.8f, 1.0f, 1.0f) );
		GLDefaultFrameBuffer::ClearDepth( 1.0f );

		DrawTerrain();
	}
	
/*
=================================================
	Update
=================================================
*/
	void CSampleApplication::Update ()
	{
		const float mouseSens = 0.1f;
		const float moveScale = 0.1f;

		if ( Input()->IsKeyClick( e_key::ESCAPE ) )
		{
			StopSimulation();
			Window()->Quit();
			return;
		}
		
		// camera
		{
			fvec3	move;

			move.x += Input()->IsKey( e_key::W );
			move.x -= Input()->IsKey( e_key::S );
			move.y += Input()->IsKey( e_key::D );
			move.y -= Input()->IsKey( e_key::A );
			move.z += Input()->IsKey( e_key::SPACE );
			move.z -= Input()->IsKey( e_key::L_SHIFT );

			camera.MoveFPSFree( move * moveScale );
		}

		if ( Input()->IsKey( e_key::M_LEFT ) )
		{
			camera.RotateFPS( Input()->GetMouseDelta() * mouseSens );
		
			Input()->CursorToCenter();

			if ( not cursorIsHidden )
			{
				cursorIsHidden = true;
				Window()->ShowCursor( false );
			}
		}
		else
		if ( cursorIsHidden )
		{
			cursorIsHidden = false;
			Window()->ShowCursor( true );
		}
		
		// terrain height
		if ( Input()->IsKeyClick( e_key::R_BRACKET ) )	terrainData.ub.EditData().fHeightScale += TerrainUB::HeightScaleStep();
		if ( Input()->IsKeyClick( e_key::L_BRACKET ) )	terrainData.ub.EditData().fHeightScale -= TerrainUB::HeightScaleStep();

		// detail level
		if ( Input()->IsKeyClick( e_key::PERIOD ) )		terrainData.ub.EditData().fDetailLevel += TerrainUB::DetailLevelStep();
		if ( Input()->IsKeyClick( e_key::COMMA ) )		terrainData.ub.EditData().fDetailLevel -= TerrainUB::DetailLevelStep();
		
		// tessellation level
		if ( Input()->IsKeyClick( e_key::PLUS ) )		terrainData.ub.EditData().fMaxTessLevel += TerrainUB::TessLevelStep();
		if ( Input()->IsKeyClick( e_key::MINUS ) )		terrainData.ub.EditData().fMaxTessLevel -= TerrainUB::TessLevelStep();
		
		// grid scale
		if ( Input()->IsKeyClick( e_key::_0 ) )			terrainData.ub.EditData().fGridScale += TerrainUB::GridScaleStep();
		if ( Input()->IsKeyClick( e_key::_9 ) )			terrainData.ub.EditData().fGridScale -= TerrainUB::GridScaleStep();


		// vsync
		if ( Input()->IsKeyClick( e_key::V ) )
		{
			vsyncEnabled = not vsyncEnabled;
			Window()->SetVSync( vsyncEnabled );
		}

		// wireframe
		if ( Input()->IsKeyClick( e_key::P ) )
		{
			wireframe = not wireframe;
			terrainData.rs.ePolygon = wireframe ? gl_polygon_mode::LINE : gl_polygon_mode::FILL;
		}


		// terrain simulation
		if ( Input()->IsKeyClick( e_key::F1 ) )
			StartSimulation( "cl/init_voxel_terrain_1.cl", ComputePass::WRITE, 1 );

		if ( Input()->IsKeyClick( e_key::F2 ) )
			StartSimulation( "cl/simulate_voxel_terrain_1.cl", ComputePass::READ_WRITE, 1, true );

		if ( Input()->IsKeyClick( e_key::O ) )
		{
			if ( simulation.IsSimulationPaused() )
				simulation.ResumeSimulation();
			else
				simulation.PauseSimulation();
		}


		simulation.Update();
	}
	
/*
=================================================
	OnResize
=================================================
*/
	void CSampleApplication::OnResize ()
	{
		const fvec2 wnd_size = Window()->Descriptor().vSize.To<fvec2>();
		camera.Resize( DTR(60.0f), wnd_size.x/wnd_size.y );
		SMAN()->Viewport( Window()->Descriptor().vSize.To<ivec2>() );
	}
	
/*
=================================================
	Run
=================================================
*/
	bool CSampleApplication::Run ()
	{
		CSampleApplication	app;

		return
			app._Run( "3-VoxelTerrain/data",
					  CWindow::TDescriptor( uvec2(640,480), ivec2(), e_wnd_style::DEF_WINDOWED, 0, false ),
					  GLWindow::TGLDescriptor( e_glinit_flags::DEFAULT_CORE, 32, 24, 0, 8, 4, true, null ) );
	}
	
/*
=================================================
	StartSimulation
=================================================
*/
	bool CSampleApplication::StartSimulation (uni_c_string program, uint flags, uint requireUpdates, bool genHeightMaps)
	{
		if ( genHeightMaps )
			simulation.StartSimulation( program, flags, requireUpdates, eventListener, heightMaps );
		else
			simulation.StartSimulation( program, flags, requireUpdates, eventListener );

		return true;
	}
	
/*
=================================================
	StopSimulation
=================================================
*/
	void CSampleApplication::StopSimulation ()
	{
		simulation.StopSimulation();
	}
	
/*
=================================================
	DrawTerrain
=================================================
*/
	void CSampleApplication::DrawTerrain ()
	{
		if ( heightMap.IsNull() )
			return;

		const fmat4	terrain_scale		= fmat4::Identity();
		const fmat4	terrain_rotation	= fmat4::FromQuat( fquat().RotationX( DTR(90.0f) ) );
		const fmat4	terrain_model_mat	= camera.GetModelMatrix() * terrain_rotation * terrain_scale;
		const fmat4	mvp					= camera.ViewProjMatrix() * terrain_model_mat;

		terrainData.rs.Apply();

		terrainData.prog->Bind();
		SMAN()->EnableTessellation( terrainData.patchSize != 0, terrainData.patchSize );

		terrainData.ub.Update();
		terrainData.matMVP.SetValue( mvp );

		heightMap->Bind(0);
		terrainData.unHeightMap.SetValue(0);

		grid->DrawAll();
		SMAN()->EnableTessellation( false, 0 );
	}
	



//-------------------------- SimulationEventListener ------------------------//
/*
=================================================
	OnCreate
=================================================
*/
	bool CSampleApplication::SimulationEventListener::OnCreate ()
	{
		return true;
	}
	
/*
=================================================
	BeforeRunProgram
=================================================
*/
	void CSampleApplication::SimulationEventListener::BeforeRunProgram (CLQueue *, CLKernel *, const ivec3 &)
	{
	}
	
/*
=================================================
	AfterRunProgram
=================================================
*/
	void CSampleApplication::SimulationEventListener::AfterRunProgram (CLQueue *, CLImage *, CLImage *, const ivec3 &)
	{
	}
	
/*
=================================================
	HeightMapUpdated
=================================================
*/
	void CSampleApplication::SimulationEventListener::HeightMapUpdated (const GLTexture2DPtr &tex)
	{
		_app->heightMap = tex;
	}

